<!DOCTYPE html>
<title>@scope - evaluation</title>
<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>

function test_scope(script_element, callback_fn, description) {
  test((t) => {
    // The provided <script> element must be an immedate subsequent sibling of
    // a <template> element.
    let template_element = script_element.previousElementSibling;
    assert_equals(template_element.tagName, 'TEMPLATE');

    t.add_cleanup(() => main.replaceChildren());

    main.append(template_element.content.cloneNode(true));

    callback_fn();
  }, description);
}

function assert_green(selector) {
  assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 128, 0)');
}
function assert_not_green(selector) {
  assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 0, 0)');
}
</script>
<style>
  :where(main *) {
    background-color: black;
  }
</style>
<main id=main>
</main>

<!-- Tests follow -->

<template>
  <style>
    @scope (.a) {
      span { background-color: green; }
    }
  </style>
  <div class=a>
    <span>green</span>
  </div>
  <div class=b>
    <span>not green</span>
  </div>
  <span>not green</span>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a > span');
  assert_not_green('.b > span');
  assert_not_green(':scope > span');
}, 'Single scope');
</script>

<template>
  <style>
    @scope (.a) {
      .a { background-color: green; }
    }
  </style>
  <div class=a> <!-- green -->
    <span>not green</span>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('.a');
  assert_not_green('.a > span');
}, 'Scope can not match its own root without :scope');
</script>

<template>
  <style>
    @scope (.a) {
      :scope { background-color: green; }
    }
  </style>
  <div class=a> <!-- green -->
    <span>not green</span>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a');
  assert_not_green('.a > span');
}, 'Selecting self with :scope');
</script>

<template>
  <style>
    @scope (.a) to (.c) {
      span { background-color: green; }
    }
  </style>
  <div class=a>
    <div class=b>
      <span>green</span>
    </div>
    <div class=c>
      <span>not green</span>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.b > span');
  assert_not_green('.c > span');
}, 'Single scope with limit');
</script>

<template>
  <style>
    @scope (.a) {
      :scope > span { background-color: green; }
    }
  </style>
  <div class=a>
    <span>green</span>
    <div class=b>
      <span>not green</span>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a > span');
  assert_not_green('.b > span');
}, 'Single scope, :scope pseudo in main selector');
</script>

<template>
  <style>
    @scope (.a) to (:scope > .b) {
      span { background-color: green; }
    }
  </style>
  <div class=a>
    <div class=b>
      <span>not green</span>
    </div>
    <div class=c>
      <div class=b>
        <span>green</span>
      </div>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('.a > .b > span');
  assert_green('.a > .c > .b > span');
}, 'Single scope, :scope pseudo in to-selector');
</script>

<template>
  <style>
    @scope (.a) to (:scope > .b) {
      span { background-color: green; }
    }
  </style>
  <div class=a>
    <div class=b>
      <span>not green</span>
    </div>
    <div class=a>
      <div class=b>
        <span>green</span>
      </div>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('.a > .b > span');
  // Note that this span is in the outer .a-scope, but not in the inner scope.
  assert_green('.a > .a > .b > span');
}, 'Multiple scopes, :scope pseudo in to-selector');
</script>

<template>
  <style>
    @scope (.a) {
      @scope (:scope > .b) {
        span { background-color: green; }
      }
    }
  </style>
  <div class=a>
    <div class=b>
      <span>green</span>
    </div>
    <div>
      <div class=b>
        <span>not green</span>
      </div>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a > .b > span');
  assert_not_green('.a > div > .b > span');
}, 'Inner @scope with :scope in from-selector');
</script>

<template>
  <style>
    @scope (.a) to (:scope > .b) {
      .c { background-color: green; }
    }
  </style>
  <div class=a>
    <div>
      <div class=a>
        <div class=b>
          <div class=c></div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  // Not in the inner scope, but is in the outer scope.
  assert_green('.c');
}, 'Multiple scopes from same @scope-rule, only one limited');
</script>

<template>
  <style>
    @scope (.a) to (.b) {
      .c { background-color: green; }
    }
  </style>
  <div class=a>
    <div>
      <div class=a>
        <div class=b>
          <div class=c></div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('.c');
}, 'Multiple scopes from same @scope-rule, both limited');
</script>

<template>
  <style>
    @scope (.a) {
      @scope (.b) {
        span { background-color: green; }
      }
    }
  </style>
  <div class=a>
    <div class=b>
      <span>green</span>
    </div>
    <span>not green</span>
  </div>
  <div class=b>
    <span>not green</span>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a > .b > span');
  assert_not_green('.a > span');
  assert_not_green(':scope > .b > span');
}, 'Nested scopes');
</script>

<template>
  <style>
    @scope (.b) {
      @scope (.a) {
        span { background-color: green; }
      }
    }
  </style>
  <div class=a>
    <div class=b>
      <span>not green</span>
    </div>
    <span>not green</span>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('.a > .b > span');
  assert_not_green('.a > span');
}, 'Nested scopes, reverse');
</script>


<template>
  <style>
    @scope (.a) {
      @scope (.b) to (.c) {
        span { background-color: green; }
      }
    }
  </style>
  <div class=a>
    <div class=b>
      <span>green</span>
    </div>
    <div class=b>
      <div class=c>
        <span>not green</span>
      </div>
    </div>
    <span>not green</span>
  </div>
  <div class=b>
    <span>not green</span>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a > .b > span');
  assert_not_green('.a > span');
  assert_not_green('.a > .b > .c > span');
  assert_not_green(':scope > .b > span');
}, 'Nested scopes, with to-selector');
</script>

<template>
  <style>
    @scope (.a) {
      :scope { background-color: green; }
    }
  </style>
  <div class=a></div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a');
}, ':scope selecting itself');
</script>

<template>
  <style>
    @scope (.a) to (.b) {
      * { background-color: green; }
    }
  </style>
  <div id=above>
    <div class=a>
      <div>
        <div class=b>
          <div id=below></div>
        </div>
      </div>
    </div>
    <div id=adjacent></div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('#above');
  assert_not_green('#adjacent');
  assert_not_green('.a');
  assert_green('.a > div');
  assert_not_green('.b');
  assert_not_green('#below');
}, 'The scoping limit is not in scope');
</script>

<template>
  <style>
    @scope (.a) to (.b > *) {
      * { background-color: green; }
    }
  </style>
  <div id=above>
    <div class=a>
      <div>
        <div class=b>
          <div id=limit></div>
        </div>
      </div>
    </div>
    <div id=adjacent></div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('#above');
  assert_not_green('#adjacent');
  assert_not_green('.a');
  assert_green('.a > div');
  assert_green('.b');
  assert_not_green('#limit');
}, 'Simulated inclusive scoping limit');
</script>

<template>
  <style>
    @scope (.a) to (:scope) {
      * { background-color: green; }
    }
  </style>
  <div id=above>
    <div class=a>
      <div>
        <div class=b>
          <div id=inner></div>
        </div>
      </div>
    </div>
    <div id=adjacent></div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('#above');
  assert_not_green('#adjacent');
  assert_not_green('.a');
  assert_not_green('.a > div');
  assert_not_green('.b');
  assert_not_green('#inner');
}, 'Scope with no elements');
</script>


<template>
  <style>
    @scope (.a) {
      :scope + .c { background-color: green; }
    }
  </style>
  <div class=a>
    <div class=a></div>
    <div class=c></div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  // A :scope sibling can never match, as the scoping element must
  // be on the ancestor chain.
  assert_not_green('.c');
}, ':scope direct adjacent sibling');
</script>


<template>
  <style>
    @scope (.a) {
      :scope + .c { background-color: green; }
    }
  </style>
  <div class=a>
    <div class=a></div>
    <div></div>
    <div class=c></div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  // A :scope sibling can never match, as the scoping element must
  // be on the ancestor chain.
  assert_not_green('.c');
}, ':scope indirect adjacent sibling');
</script>


<template>
  <style>
    @scope (.a) {
      > span { background-color: green; }
    }
  </style>
  <div class=a>
    <span>green</span>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('.a > span');
}, 'Relative selector inside @scope');
</script>


<template>
  <style>
    @scope (.a) {
      /* Can never match anything. */
      :scope > :scope { background-color: green; }
    }
  </style>
  <div class=a>
    <div id=inner class=a>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('.a');
  assert_not_green('#inner');
}, ':scope in two different compounds');
</script>

<template>
  <style>
    @scope (.a:has(.c)) {
      .b { background-color:green; }
    }
  </style>
  <div class=first>
    <div class=a>
      <div class=b>
        <div class=c></div>
      </div>
    </div>
  </div>
  <div class=second>
    <div class=a>
      <div class=b>
        <div class=d></div>
      </div>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_not_green('.first .a');
  assert_green('.first .b');
  assert_not_green('.first .c');

  assert_not_green('.second .a');
  assert_not_green('.second .b');
  assert_not_green('.second .d');
}, 'Scope root with :has()');
</script>

<template>
  <style>
    @scope (.a) to (.b, .c) {
      * { background-color:green; }
    }
  </style>
  <div class=a>
    <span id="in"></span>
    <div class=b>
      <span id="out"></span>
      <div class=c></div>
    </div>
  </div>
</template>
<script>
test_scope(document.currentScript, () => {
  assert_green('#in');
  assert_not_green('#out');
}, 'Any scope limit makes the element out of scope');
</script>
